package com.wondersgroup.flutter.nfc_sbk;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.Ndef;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;

/**
 * NfcInFlutterPlugin
 */
public class NfcSbkPlugin implements MethodCallHandler,
        EventChannel.StreamHandler,
        PluginRegistry.NewIntentListener,
        NfcAdapter.ReaderCallback {

  private static final String NORMAL_READER_MODE = "normal";
  private static final String DISPATCH_READER_MODE = "dispatch";
  private final int DEFAULT_READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B | NfcAdapter.FLAG_READER_NFC_F | NfcAdapter.FLAG_READER_NFC_V;
  private static final String LOG_TAG = "NfcInFlutterPlugin";

  private final Activity activity;
  private NfcAdapter adapter;
  private EventChannel.EventSink events;

  private String cardInfo;
  private Object arguments;
  private String currentReaderMode = null;

  /**
   * Plugin registration.
   */
  public static void registerWith(Registrar registrar) {
    final MethodChannel channel = new MethodChannel(registrar.messenger(), "nfc_sbk");
    final EventChannel tagChannel = new EventChannel(registrar.messenger(), "nfc_sbk/tags");
    NfcSbkPlugin plugin = new NfcSbkPlugin(registrar.activity());
    registrar.addNewIntentListener(plugin);
    channel.setMethodCallHandler(plugin);
    tagChannel.setStreamHandler(plugin);
  }

  private NfcSbkPlugin(Activity activity) {
    this.activity = activity;
  }

  @Override
  public void onMethodCall(MethodCall call, Result result) {
    switch (call.method) {
      case "getPlatformVersion":
        result.success("Android " + android.os.Build.VERSION.RELEASE);
        break;
      case "readNDEFSupported":
        result.success(nfcIsEnabled());
        break;
      case "startNDEFReading":
        if (!(call.arguments instanceof HashMap)) {
          result.error("MissingArguments", "startNDEFReading was called with no arguments", "");
          return;
        }
        HashMap args = (HashMap) call.arguments;
        String readerMode = (String) args.get("reader_mode");
        if (readerMode == null) {
          result.error("MissingReaderMode", "startNDEFReading was called without a reader mode", "");
          return;
        }

        if (currentReaderMode != null && !readerMode.equals(currentReaderMode)) {
          // Throw error if the user tries to start reading with another reading mode
          // than the one currently active
          result.error("NFCMultipleReaderModes", "multiple reader modes", "");
          return;
        }
        currentReaderMode = readerMode;
        switch (readerMode) {
          case NORMAL_READER_MODE:
            boolean noSounds = (boolean) args.get("no_platform_sounds");
            startReading(noSounds);
            break;
          case DISPATCH_READER_MODE:
            startReadingWithForegroundDispatch();
            break;
          default:
            result.error("NFCUnknownReaderMode", "unknown reader mode: " + readerMode, "");
            return;
        }
        result.success(null);
        break;
      case "writeIsoDep":
        arguments=call.arguments;
        startReading(false);
        result.success(cardInfo);
        cardInfo=null;
        break;
      case "stopNFC":
        cardInfo=null;
        arguments=null;
        //this.activity.finish();
        //onCancel(null);
        result.success(null);
        break;
      case "checkNFC":
        String _result="";
        if (adapter == null) adapter = NfcAdapter.getDefaultAdapter(activity);
        if(adapter==null){
          _result="NOT_SUPPORT";
        }else{
          if (!adapter.isEnabled()){
            _result="NOT_ENABLE";
          }else{
            _result="ENABLE";
          }
        }
        result.success(_result);
        break;
      case "encryptDataBySM4":
          arguments=call.arguments;
          List<String> args1=(ArrayList)arguments;
          String cipherText=SM4Utils.encryFromHex(args1);
          result.success(cipherText);
          break;
     /* case "decryptDataBySM4":
        arguments=call.arguments;
        List<String> args2=(ArrayList)arguments;
        sm4.secretKey = args2.get(0);//密钥
        sm4.hexString = args2.get(1);//是否是16进制串
        String plainText = sm4.decryptData_ECB_byte(Util.hexToByte(args2.get(2)));
        System.out.println("明文from Hex: " + plainText);
        result.success(plainText);
      break;*/
      default:
        result.notImplemented();
    }
  }

  private Boolean nfcIsEnabled() {
    NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity);
    if (adapter == null) return false;
    return adapter.isEnabled();
  }

  private void startReading(boolean noSounds) {
    if (adapter == null) adapter = NfcAdapter.getDefaultAdapter(activity);
    if (adapter == null) return;
    Bundle bundle = new Bundle();
    int flags = DEFAULT_READER_FLAGS;
    if (noSounds) {
      flags = flags | NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS;
    }
    adapter.enableReaderMode(activity, this, flags, bundle);
  }

  private void startReadingWithForegroundDispatch() {
    adapter = NfcAdapter.getDefaultAdapter(activity);
    if (adapter == null) return;
    Intent intent = new Intent(activity.getApplicationContext(), activity.getClass());
    intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

    PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0);
    String[][] techList = new String[][]{};

    adapter.enableForegroundDispatch(activity, pendingIntent, null, techList);
  }

  @Override
  public void onListen(Object args, EventChannel.EventSink eventSink) {
    events = eventSink;
  }

  @Override
  public void onCancel(Object args) {
    switch (currentReaderMode) {
      case NORMAL_READER_MODE:
        adapter.disableReaderMode(activity);
        break;
      case DISPATCH_READER_MODE:
        adapter.disableForegroundDispatch(activity);
        break;
      default:
        Log.e(LOG_TAG, "unknown reader mode: " + currentReaderMode);
    }
    events = null;
    currentReaderMode = null;
  }
  @Override
  public void onTagDiscovered(Tag tag) {
    System.out.println("---plugin---"+tag.toString());
    IsoDep isoDep = IsoDep.get(tag);
    String type="";
    if (isoDep != null) {
      try {
       isoDep.connect();//连接
       List<String> argumentss=(ArrayList)arguments;
       System.out.println("--argumentss:"+argumentss);
       type=argumentss.get(0);
       List<String> args=argumentss.subList(1,argumentss.size());

          if("A".equals(type)){//如果是读卡
            cardInfo=readSbk(isoDep,args);
          }
          if("B".equals(type)){//如果是私钥签名
            cardInfo=signSbk(isoDep,args);
          }

        eventSuccess(cardInfo);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * 读社保卡，逐条写命令
   * @param isoDep
   * @param args
   * @return
   */
  public String readSbk(IsoDep isoDep,List<String> args)throws IOException{
    String responseArray="";
    byte[] resp;
    for(String arg:args){
      resp = isoDep.transceive(HexUtils.HexStringToByteArray(arg));
      if(responseArray.equals(""))
        responseArray=responseArray+(HexUtils.ByteArrayToHexString(resp));
      else responseArray=responseArray+","+(HexUtils.ByteArrayToHexString(resp));
    }
    System.out.println("--responseArray:"+responseArray);
    return responseArray;
  }

  /**
   * 私钥签名
   * @param isoDep
   * @param args
   * @return
   */
  public String signSbk(IsoDep isoDep,List<String> arg)throws IOException{
    String responseArray="";
    byte[] resp;
      resp = isoDep.transceive(HexUtils.HexStringToByteArray(arg.get(0)));
      responseArray=(HexUtils.ByteArrayToHexString(resp));
      resp = isoDep.transceive(HexUtils.HexStringToByteArray(arg.get(1)));
      responseArray=responseArray+","+(HexUtils.ByteArrayToHexString(resp));
      resp = isoDep.transceive(HexUtils.HexStringToByteArray(arg.get(2)));
      responseArray=responseArray+","+(HexUtils.ByteArrayToHexString(resp));

      System.out.println("--responseArray:"+responseArray);

      String plainText=( responseArray.split(",")[2]).substring(0,32);
      List<String> list=new ArrayList<String>();
      list.add(arg.get(3));
      list.add(plainText);
      String cipherText=SM4Utils.encryFromHex(list);
      System.out.println("--cipherText:"+cipherText);
      resp=isoDep.transceive(HexUtils.HexStringToByteArray("0020018110"+cipherText.substring(0,32)));
      responseArray=HexUtils.ByteArrayToHexString(resp);
      System.out.println("--pin:"+responseArray);
      if("9000".equals(responseArray)){
        resp=isoDep.transceive(HexUtils.HexStringToByteArray("804E000028C2020015C1820020"+arg.get(4)));
        responseArray=HexUtils.ByteArrayToHexString(resp);
        System.out.println("--sign:"+responseArray);
      }
    return responseArray;
  }

  public void onTagDiscovered_back(Tag tag) {
    Ndef ndef = Ndef.get(tag);
    if (ndef == null) {
      // tag is not in NDEF format; skip!
      return;
    }
    try {
      ndef.connect();
      NdefMessage message = ndef.getNdefMessage();
      if (message == null) {
        return;
      }
      eventSuccess(formatNDEFMessageToResult(ndef, message));
    } catch (IOException e) {
      Map<String, Object> details = new HashMap<>();
      details.put("fatal", true);
      eventError("IOError", e.getMessage(), details);
    } catch (FormatException e) {
      eventError("NDEFBadFormatError", e.getMessage(), null);
    } finally {
      try {
        ndef.close();
      } catch (IOException e) {
        Map<String, Object> details = new HashMap<>();
        details.put("fatal", false);
        eventError("IOError", e.getMessage(), details);
      }
    }
  }

  @Override
  public boolean onNewIntent(Intent intent) {
    String action = intent.getAction();
    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
      Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
      handleNDEFTagFromIntent(tag);
      return true;
    }
    return false;
  }

  private void handleNDEFTagFromIntent(Tag tag) {
    Ndef ndef = Ndef.get(tag);
    if (ndef == null) {
      return;
    }

    NdefMessage message = ndef.getCachedNdefMessage();
    eventSuccess(formatNDEFMessageToResult(ndef, message));
  }

  private Map<String, Object> formatNDEFMessageToResult(Ndef ndef, NdefMessage message) {
    final Map<String, Object> result = new HashMap<>();
    List<Map<String, String>> records = new ArrayList<>();
    for (NdefRecord record : message.getRecords()) {
      Map<String, String> recordMap = new HashMap<>();
      recordMap.put("payload", new String(record.getPayload(), StandardCharsets.UTF_8));
      recordMap.put("id", new String(record.getId(), StandardCharsets.UTF_8));
      recordMap.put("type", new String(record.getType(), StandardCharsets.UTF_8));
      recordMap.put("tnf", String.valueOf(record.getTnf()));
      records.add(recordMap);
    }
    byte[] idByteArray = ndef.getTag().getId();
    // Fancy string formatting snippet is from
    // https://gist.github.com/luixal/5768921#gistcomment-1788815
    result.put("id", String.format("%0" + (idByteArray.length * 2) + "X", new BigInteger(1, idByteArray)));
    result.put("message_type", "ndef");
    result.put("type", ndef.getType());
    result.put("records", records);
    return result;
  }

  private void eventSuccess(final Object result) {
    Handler mainThread = new Handler(activity.getMainLooper());
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        if (events != null) {
          // Event stream must be handled on main/ui thread
          events.success(result);
        }
      }
    };
    mainThread.post(runnable);
  }

  private void eventError(final String code, final String message, final Object details) {
    Handler mainThread = new Handler(activity.getMainLooper());
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        if (events != null) {
          // Event stream must be handled on main/ui thread
          events.error(code, message, details);
        }
      }
    };
    mainThread.post(runnable);
  }
}